package ru.niir.goodfil.audit.util;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.*;

public class EntityUtils {
    public static StringablePropertiesList getStringablePropertiesList(Object o) {
        Assert.notNull(o);

        Class klass = o.getClass();

        List<StringableProperty> properties = new ArrayList<StringableProperty>();
        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(klass);
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method method = propertyDescriptor.getReadMethod();
            if (method == null) {
                continue;
            }

            Object oValue = null;

            try {
                oValue = method.invoke(o);
            } catch (Exception e) {
            }

            properties.add(new StringableProperty(propertyDescriptor.getName(), String.valueOf(oValue)));
        }

        return new StringablePropertiesList(properties);
    }

    public static List<PropertyHistory> compareEntities(Object o1, Object o2) {
        Assert.notNull(o1);
        Assert.notNull(o2);

        Class klass1 = o1.getClass();
        Class klass2 = o2.getClass();
        Assert.isTrue(klass1.equals(klass2));

        List<PropertyHistory> propertyHistories = new ArrayList<PropertyHistory>();

        PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(klass1);
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            Method method = propertyDescriptor.getReadMethod();
            Object o1Value = null;
            Object o2Value = null;

            try {
                o1Value = method.invoke(o1);
                o2Value = method.invoke(o2);
            } catch (Exception e) {
            }

            if (o1Value == null && o2Value == null) {
                continue;
            } else if (o1Value == null) {
                PropertyHistory propertyHistory = new PropertyHistory(propertyDescriptor.getName(),
                        null,
                        o2Value);
                propertyHistories.add(propertyHistory);
            } else if (o2Value == null) {
                PropertyHistory propertyHistory = new PropertyHistory(propertyDescriptor.getName(),
                        o1Value,
                        null);
                propertyHistories.add(propertyHistory);
            } else if (o1Value instanceof String ||
                    o1Value instanceof Float ||
                    o1Value instanceof Double ||
                    o1Value instanceof Boolean ||
                    o1Value instanceof Integer ||
                    o1Value instanceof BigInteger ||
                    o1Value instanceof Long ||
                    o1Value instanceof BigDecimal ||
                    o1Value instanceof Timestamp ||
                    o1Value instanceof java.util.Date ||
                    o1Value instanceof java.sql.Date) {
                if (!o1Value.equals(o2Value)) {
                    PropertyHistory propertyHistory = new PropertyHistory(propertyDescriptor.getName(),
                            o1Value,
                            o2Value);
                    propertyHistories.add(propertyHistory);
                }
            } else if (o1Value instanceof Collection) {
                Collection collection1 = (Collection) o1Value;
                Iterator i1 = collection1.iterator();
                Set<String> set1 = new HashSet<String>();
                while (i1.hasNext()) {
                    set1.add(String.valueOf(i1.next()));
                }
                String setString1 = StringUtils.join(set1, ", ");

                Collection collection2 = (Collection) o2Value;
                Iterator i2 = collection2.iterator();
                Set<String> set2 = new HashSet<String>();
                while (i2.hasNext()) {
                    set2.add(String.valueOf(i2.next()));
                }
                String setString2 = StringUtils.join(set2, ", ");

                if (!setString1.equals(setString2)) {
                    PropertyHistory propertyHistory = new PropertyHistory(propertyDescriptor.getName(),
                            setString1,
                            setString2);
                    propertyHistories.add(propertyHistory);
                }
            } else if (o1Value instanceof Map) {
                Map map1 = (Map) o1Value;
                Iterator i1 = map1.keySet().iterator();
                Set<String> set1 = new HashSet<String>();
                while (i1.hasNext()) {
                    Object key = i1.next();
                    Object value = map1.get(key);

                    String keyString = String.valueOf(key);
                    String valueString = String.valueOf(value);
                    set1.add("[" + keyString + ":" + valueString + "]");
                }

                Map map2 = (Map) o2Value;
                Iterator i2 = map2.keySet().iterator();
                Set<String> set2 = new HashSet<String>();
                while (i2.hasNext()) {
                    Object key = i2.next();
                    Object value = map2.get(key);

                    String keyString = String.valueOf(key);
                    String valueString = String.valueOf(value);
                    set2.add("[" + keyString + ":" + valueString + "]");
                }

                String setString1 = StringUtils.join(set1, ", ");
                String setString2 = StringUtils.join(set2, ", ");

                if (!setString1.equals(setString2)) {
                    PropertyHistory propertyHistory = new PropertyHistory(propertyDescriptor.getName(),
                            setString1,
                            setString2);
                    propertyHistories.add(propertyHistory);
                }

            } else if (!o1Value.equals(o2Value)) {
                List<PropertyHistory> tmp = compareEntities(o1Value, o2Value);
                List<PropertyHistory> inner = new ArrayList<PropertyHistory>();
                for (PropertyHistory propertyHistory : tmp) {
                    PropertyHistory propertyHistory2 = new PropertyHistory(propertyDescriptor.getName() + "." + propertyHistory.getPropertyName(),
                            propertyHistory.getOldValue(),
                            propertyHistory.getNewValue());
                    inner.add(propertyHistory2);
                }
                propertyHistories.addAll(inner);
            }
        }

        return propertyHistories;
    }
}
